/*
 * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC")
 * Copyright (c) 1996,1999 by Internet Software Consortium.
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
 * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 *	  src/backend/utils/adt/inet_net_pton.c
 */

#if defined(LIBC_SCCS) && !defined(lint)
static const char rcsid[] = "Id: inet_net_pton.c,v 1.4.2.3 2004/03/17 00:40:11 marka Exp $";
#endif

#include "postgres.h"
#include "knl/knl_variable.h"

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <ctype.h>

#include "utils/builtins.h" /* pgrminclude ignore */ /* needed on some
														 * platforms */
#include "utils/inet.h"

static int inet_net_pton_ipv4(const char* src, u_char* dst);
static int inet_cidr_pton_ipv4(const char* src, u_char* dst, size_t size);
static int inet_net_pton_ipv6(const char* src, u_char* dst);
static int inet_cidr_pton_ipv6(const char* src, u_char* dst, size_t size);

/*
 * int
 * inet_net_pton(af, src, dst, size)
 *	convert network number from presentation to network format.
 *	accepts hex octets, hex strings, decimal octets, and /CIDR.
 *	"size" is in bytes and describes "dst".
 * return:
 *	number of bits, either imputed classfully or specified with /CIDR,
 *	or -1 if some failure occurred (check errno).  ENOENT means it was
 *	not a valid network specification.
 *
 * Changes:
 *	I added the inet_cidr_pton function (also from Paul) and changed
 *	the names to reflect their current use.
 *
 */
int inet_net_pton(int af, const char* src, void* dst, size_t size)
{
    switch (af) {
        case PGSQL_AF_INET:
            return (int)size == -1 ? inet_net_pton_ipv4(src, (u_char*)dst)
                                   : inet_cidr_pton_ipv4(src, (u_char*)dst, size);
        case PGSQL_AF_INET6:
            return (int)size == -1 ? inet_net_pton_ipv6(src, (u_char*)dst)
                                   : inet_cidr_pton_ipv6(src, (u_char*)dst, size);
        default:
            errno = EAFNOSUPPORT;
            return (-1);
    }
}

/*
 * static int
 * inet_cidr_pton_ipv4(src, dst, size)
 *	convert IPv4 network number from presentation to network format.
 *	accepts hex octets, hex strings, decimal octets, and /CIDR.
 *	"size" is in bytes and describes "dst".
 * return:
 *	number of bits, either imputed classfully or specified with /CIDR,
 *	or -1 if some failure occurred (check errno).  ENOENT means it was
 *	not an IPv4 network specification.
 * note:
 *	network byte order assumed.  this means 192.5.5.240/28 has
 *	0b11110000 in its fourth octet.
 */
static int inet_cidr_pton_ipv4(const char* src, u_char* dst, size_t size)
{
    static const char xdigits[] = "0123456789abcdef";
    static const char digits[] = "0123456789";
    int n, ch, dirty, bits;
    unsigned int tmp = 0;

    const u_char* odst = dst;

    ch = *src++;
    if (ch == '0' && (src[0] == 'x' || src[0] == 'X') && isxdigit((unsigned char)src[1])) {
        /* Hexadecimal: Eat nybble string. */
        if (size <= 0U) {
            goto emsgsize;
        }
        dirty = 0;
        src++; /* skip x or X. */
        while ((ch = *src++) != '\0' && isxdigit((unsigned char)ch)) {
            if (isupper((unsigned char)ch)) {
                ch = tolower((unsigned char)ch);
            }
            n = strchr(xdigits, ch) - xdigits;
            Assert(n >= 0 && n <= 15);
            if (dirty == 0) {
                tmp = n;
            } else {
                tmp = (tmp << 4) | (unsigned int)n;
            }
            if (++dirty == 2) {
                if (size-- <= 0U) {
                    goto emsgsize;
                }
                *dst++ = (u_char)tmp;
                dirty = 0;
            }
        }
        if (dirty) { /* Odd trailing nybble? */
            if (size-- <= 0U) {
                goto emsgsize;
            }
            *dst++ = (u_char)(tmp << 4);
        }
    } else if (isdigit((unsigned char)ch)) {
        /* Decimal: eat dotted digit string. */
        for (;;) {
            tmp = 0;
            do {
                n = strchr(digits, ch) - digits;
                Assert(n >= 0 && n <= 9);
                tmp *= 10;
                tmp += n;
                if (tmp > 255) {
                    goto enoent;
                }
            } while ((ch = *src++) != '\0' && isdigit((unsigned char)ch));
            if (size-- <= 0U) {
                goto emsgsize;
            }
            *dst++ = (u_char)tmp;
            if (ch == '\0' || ch == '/') {
                break;
            }
            if (ch != '.') {
                goto enoent;
            }
            ch = *src++;
            if (!isdigit((unsigned char)ch)) {
                goto enoent;
            }
        }
    } else {
        goto enoent;
    }

    bits = -1;
    if (ch == '/' && isdigit((unsigned char)src[0]) && dst > odst) {
        /* CIDR width specifier.  Nothing can follow it. */
        ch = *src++; /* Skip over the /. */
        bits = 0;
        do {
            n = strchr(digits, ch) - digits;
            Assert(n >= 0 && n <= 9);
            bits *= 10;
            bits += n;
        } while ((ch = *src++) != '\0' && isdigit((unsigned char)ch));
        if (ch != '\0') {
            goto enoent;
        }
        if (bits > 32) {
            goto emsgsize;
        }
    }

    /* Firey death and destruction unless we prefetched EOS. */
    if (ch != '\0') {
        goto enoent;
    }

    /* If nothing was written to the destination, we found no address. */
    if (dst == odst) {
        goto enoent;
    }
    /* If no CIDR spec was given, infer width from net class. */
    if (bits == -1) {
        if (*odst >= 240) {
            /* Class E */
            bits = 32;
        } else if (*odst >= 224) {
            /* Class D */
            bits = 8;
        } else if (*odst >= 192) {
            /* Class C */
            bits = 24;
        } else if (*odst >= 128) {
            /* Class B */
            bits = 16;
        } else {
            /* Class A */
            bits = 8;
        }
        /* If imputed mask is narrower than specified octets, widen. */
        if (bits < ((dst - odst) * 8)) {
            bits = (dst - odst) * 8;
        }

        /*
         * If there are no additional bits specified for a class D address
         * adjust bits to 4.
         */
        if (bits == 8 && *odst == 224) {
            bits = 4;
        }
    }
    /* Extend network to cover the actual mask. */
    while (bits > ((dst - odst) * 8)) {
        if (size-- <= 0U) {
            goto emsgsize;
        }
        *dst++ = '\0';
    }
    return (bits);

enoent:
    errno = ENOENT;
    return (-1);

emsgsize:
    errno = EMSGSIZE;
    return (-1);
}

/*
 * int
 * inet_net_pton(af, src, dst, *bits)
 *	convert network address from presentation to network format.
 *	accepts inet_pton()'s input for this "af" plus trailing "/CIDR".
 *	"dst" is assumed large enough for its "af".  "bits" is set to the
 *	/CIDR prefix length, which can have defaults (like /32 for IPv4).
 * return:
 *	-1 if an error occurred (inspect errno; ENOENT means bad format).
 *	0 if successful conversion occurred.
 * note:
 *	192.5.5.1/28 has a nonzero host part, which means it isn't a network
 *	as called for by inet_cidr_pton() but it can be a host address with
 *	an included netmask.
 */
static int inet_net_pton_ipv4(const char* src, u_char* dst)
{
    static const char digits[] = "0123456789";
    const u_char* odst = dst;
    const char* chrPos = NULL;
    int n, ch, tmp, bits;
    size_t size = 4;

    /* Get the mantissa. */
    while (ch = *src++, isdigit((unsigned char)ch)) {
        tmp = 0;
        do {
            chrPos = strchr(digits, ch);
            if (unlikely(chrPos == NULL)) {
                goto enoent;
            }
            n = chrPos - digits;
            tmp *= 10;
            tmp += n;
            if (tmp > 255) {
                goto enoent;
            }
        } while ((ch = *src++) != '\0' && isdigit((unsigned char)ch));
        if (size-- == 0) {
            goto emsgsize;
        }
        *dst++ = (u_char)tmp;
        if (ch == '\0' || ch == '/') {
            break;
        }
        if (ch != '.') {
            goto enoent;
        }
    }

    /* Get the prefix length if any. */
    bits = -1;
    if (ch == '/' && isdigit((unsigned char)src[0]) && dst > odst) {
        /* CIDR width specifier.  Nothing can follow it. */
        ch = *src++; /* Skip over the /. */
        bits = 0;
        do {
            chrPos = strchr(digits, ch);
            if (unlikely(chrPos == NULL)) {
                goto enoent;
            }
            n = chrPos - digits;
            bits *= 10;
            bits += n;
        } while ((ch = *src++) != '\0' && isdigit((unsigned char)ch));
        if (ch != '\0') {
            goto enoent;
        }
        if (bits > 32) {
            goto emsgsize;
        }
    }

    /* Firey death and destruction unless we prefetched EOS. */
    if (ch != '\0') {
        goto enoent;
    }

    /* Prefix length can default to /32 only if all four octets spec'd. */
    if (bits == -1) {
        if (dst - odst == 4) {
            bits = 32;
        } else {
            goto enoent;
        }
    }

    /* If nothing was written to the destination, we found no address. */
    if (dst == odst) {
        goto enoent;
    }

    /* If prefix length overspecifies mantissa, life is bad. */
    if ((bits / 8) > (dst - odst)) {
        goto enoent;
    }

    /* Extend address to four octets. */
    while (size-- > 0) {
        *dst++ = 0;
    }

    return bits;

enoent:
    errno = ENOENT;
    return (-1);

emsgsize:
    errno = EMSGSIZE;
    return (-1);
}

static int getbits(const char* src, int* bitsp)
{
    static const char digits[] = "0123456789";
    int n;
    int val;
    char ch;

    val = 0;
    n = 0;
    while ((ch = *src++) != '\0') {
        const char* pch = NULL;

        pch = strchr(digits, ch);
        if (pch != NULL) {
            if (n++ != 0 && val == 0) {
                /* no leading zeros */
                return (0);
            }
            val *= 10;
            val += (pch - digits);
            if (val > 128) {
                /* range */
                return (0);
            }
            continue;
        }
        return (0);
    }
    if (n == 0) {
        return (0);
    }
    *bitsp = val;
    return (1);
}

static int getv4(const char* src, u_char* dst, int* bitsp)
{
    static const char digits[] = "0123456789";
    u_char* odst = dst;
    int n;
    u_int val;
    char ch;

    val = 0;
    n = 0;
    while ((ch = *src++) != '\0') {
        const char* pch = NULL;

        pch = strchr(digits, ch);
        if (pch != NULL) {
            if (n++ != 0 && val == 0) {
                /* no leading zeros */
                return (0);
            }
            val *= 10;
            val += (pch - digits);
            if (val > 255) {
                /* range */
                return (0);
            }
            continue;
        }
        if (ch == '.' || ch == '/') {
            if (dst - odst > 3) {
                /* too many octets? */
                return (0);
            }
            *dst++ = val;
            if (ch == '/') {
                return (getbits(src, bitsp));
            }
            val = 0;
            n = 0;
            continue;
        }
        return (0);
    }
    if (n == 0) {
        return (0);
    }
    if (dst - odst > 3) {
        /* too many octets? */
        return (0);
    }
    *dst++ = val;
    return (1);
}

static int inet_net_pton_ipv6(const char* src, u_char* dst)
{
    return inet_cidr_pton_ipv6(src, dst, 16);
}

#define NS_IN6ADDRSZ 16
#define NS_INT16SZ 2
#define NS_INADDRSZ 4

static int inet_cidr_pton_ipv6(const char* src, u_char* dst, size_t size)
{
    static const char xdigits_l[] = "0123456789abcdef", xdigits_u[] = "0123456789ABCDEF";
    u_char tmp[NS_IN6ADDRSZ], *tp = NULL, *endp = NULL, *colonp = NULL;
    const char *xdigits = NULL, *curtok = NULL;
    int ch, saw_xdigit;
    u_int val;
    int digits;
    int bits;
    errno_t ss_rc;

    if (size < NS_IN6ADDRSZ) {
        goto emsgsize;
    }

    ss_rc = memset_s((tp = tmp), NS_IN6ADDRSZ, '\0', NS_IN6ADDRSZ);
    securec_check(ss_rc, "\0", "\0");
    endp = tp + NS_IN6ADDRSZ;
    colonp = NULL;
    /* Leading :: requires some special handling. */
    if (*src == ':') {
        if (*++src != ':') {
            goto enoent;
        }
    }
    curtok = src;
    saw_xdigit = 0;
    val = 0;
    digits = 0;
    bits = -1;
    while ((ch = *src++) != '\0') {
        const char* pch = NULL;

        if ((pch = strchr((xdigits = xdigits_l), ch)) == NULL) {
            pch = strchr((xdigits = xdigits_u), ch);
        }
        if (pch != NULL) {
            val <<= 4;
            val |= (unsigned int)(pch - xdigits);
            if (++digits > 4) {
                goto enoent;
            }
            saw_xdigit = 1;
            continue;
        }
        if (ch == ':') {
            curtok = src;
            if (!saw_xdigit) {
                if (colonp != NULL) {
                    goto enoent;
                }
                colonp = tp;
                continue;
            } else if (*src == '\0') {
                goto enoent;
            }
            if (tp + NS_INT16SZ > endp) {
                return (0);
            }
            *tp++ = (u_char)(val >> 8) & 0xff;
            *tp++ = (u_char)val & 0xff;
            saw_xdigit = 0;
            digits = 0;
            val = 0;
            continue;
        }
        if (ch == '.' && ((tp + NS_INADDRSZ) <= endp) && getv4(curtok, tp, &bits) > 0) {
            tp += NS_INADDRSZ;
            saw_xdigit = 0;
            break; /* '\0' was seen by inet_pton4(). */
        }
        if (ch == '/' && getbits(src, &bits) > 0) {
            break;
        }
        goto enoent;
    }
    if (saw_xdigit) {
        if (tp + NS_INT16SZ > endp) {
            goto enoent;
        }
        *tp++ = (u_char)(val >> 8) & 0xff;
        *tp++ = (u_char)val & 0xff;
    }
    if (bits == -1) {
        bits = 128;
    }

    endp = tmp + 16;

    if (colonp != NULL) {
        /*
         * Since some memmove()'s erroneously fail to handle overlapping
         * regions, we'll do the shift by hand.
         */
        const int n = tp - colonp;
        int i;

        if (tp == endp) {
            goto enoent;
        }
        for (i = 1; i <= n; i++) {
            endp[-i] = colonp[n - i];
            colonp[n - i] = 0;
        }
        tp = endp;
    }
    if (tp != endp) {
        goto enoent;
    }

    /*
     * Copy out the result.
     */
    if (dst != NULL) {
        errno_t rc;
        rc = memcpy_s(dst, NS_IN6ADDRSZ, tmp, NS_IN6ADDRSZ);
        securec_check(rc, "\0", "\0");
    }

    return (bits);

enoent:
    errno = ENOENT;
    return (-1);

emsgsize:
    errno = EMSGSIZE;
    return (-1);
}
